﻿#region Namespaces

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

#endregion

namespace Vibstudio.X4NET.Text
{
    public static class StringExtensions
    {
        #region Constants

        private const string EMAIL_PATTERN = @"([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})";

        private const string URI_PATTERN = @"((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,15})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&amp;%_\./-~-]*)?";

        private const string IP_PATTERN = @"(2[0-4]\d|25[0-5]|[01]?\d\d?)\.(2[0-4]\d|25[0-5]|[01]?\d\d?)\.(2[0-4]\d|25[0-5]|[01]?\d\d?)\.(2[0-4]\d|25[0-5]|[01]?\d\d?)";

        private const string ITALY_PHONE_NUMBER_PATTERN = "[0-9]{4}[/\\.\\-][0-9]{6,7}";

        private const string CAMELCASE_PATTERN = "[A-Z]{1}[a-z]*[A-Z]{0,1}[a-z]*";

        private const string PASCALCASE_PATTERN = "[a-z]{1}[a-z]*[A-Z]{0,1}[a-z]*";

        private const string UPPERCASE_PATTERN = "[A-Z]*";

        private const string LOWERCASE_PATTERN = "[a-zàòèéùì]*";

        #endregion

        public static string Reverse(this string str)
        {
            char[] chars = str.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }

        public static string Join(this string[] array)
        {
            return string.Join("", array);
        }

        public static string[] Initials(this string value)
        {
            return Regex.Matches(value, @"\b(\w{1})")
                        .OfType<Match>()
                        .Select(m => m.Groups[1].Value)
                        .ToArray();
        }

        /// <summary>
        /// Convert the string to Pascal case.
        /// </summary>
        /// <param name="value">String value</param>
        /// <param name="separators">Separator characters</param>
        /// <returns>Resturn value in Pascal case.</returns>
        public static string ToPascalCase(this string value, params char[] separators)
        {
            // If there are 0 or 1 characters, just return the string.
            if (string.IsNullOrEmpty(value) || value.Length < 2)
            {
                return value;
            }

            char[] defaultSeparators = { ' ' };

            List<char> allSeparators = new List<char>();
            allSeparators.AddRange(defaultSeparators);
            allSeparators.AddRange(separators);

            // Split the string into words.
            string[] words = value.Split(allSeparators.ToArray(), StringSplitOptions.RemoveEmptyEntries);

            // Combine the words.
            string result = "";
            foreach (string word in words)
            {
                result += string.Format("{0}{1}", word.Substring(0, 1).ToUpper(), word.Substring(1));
            }

            return result;
        }

        /// <summary>
        /// Convert the string to camel case.
        /// </summary>
        /// <param name="value">String value</param>
        /// <param name="separators">Separator characters.</param>
        /// <returns>Return value in Camel case.</returns>
        public static string ToCamelCase(this string value, params char[] separators)
        {
            // If there are 0 or 1 characters, just return the string.
            if (string.IsNullOrEmpty(value) || value.Length < 2)
            {
                return value;
            }

            char[] defaultSeparators = { ' ' };

            List<char> allSeparators = new List<char>();
            allSeparators.AddRange(defaultSeparators);
            allSeparators.AddRange(separators);

            // Split the string into words.
            string[] words = value.Split(allSeparators.ToArray(), StringSplitOptions.RemoveEmptyEntries);

            // Combine the words.
            string result = words[0].ToLower();
            for (int i = 1; i < words.Length; i++)
            {
                result += string.Format("{0}{1}", words[i].Substring(0, 1).ToUpper(), words[i].Substring(1));
            }

            return result;
        }

        /// <summary>
        /// Use the current thread's culture info for conversion.
        /// </summary>
        /// <param name="value">String value</param>
        /// <returns>Return value in Proper case.</returns>
        public static string ToProperCase(this string value)
        {
            var cultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;
            return cultureInfo.TextInfo.ToTitleCase(value.ToLower());
        }

        public static string ToSentenceCase(this string value, bool useLetterOnly = false)
        {
            if (useLetterOnly)
            {
                value = new Regex("[^a-zA-Z]").Replace(value, " ");
            }

            var sentenceRegex = new Regex(@"(^[a-z])|[?!.:,;]\s+(.)", RegexOptions.ExplicitCapture);
            return sentenceRegex.Replace(value.ToLower(), s => s.Value.ToUpper());
        }

        public static string ToAlphaNumericOnly(this string input)
        {
            Regex rgx = new Regex("[^a-zA-Z0-9]");
            return rgx.Replace(input, "");
        }

        public static string ToAlphaOnly(this string input)
        {
            Regex rgx = new Regex("[^a-zA-Z]");
            return rgx.Replace(input, "");
        }

        public static string ToNumericOnly(this string input)
        {
            Regex rgx = new Regex("[^0-9]");
            return rgx.Replace(input, "");
        }

        public static string ReplaceAlphaOnly(this string input, string replacement)
        {
            return Replace(input, "[a-zA-Z]", replacement);
        }

        private static string Replace(this string input, string pattern, string replacement)
        {
            Regex rgx = new Regex(pattern);
            return rgx.Replace(input, replacement);
        }

        /// <summary>
        /// Checks string object's value to array of string values
        /// </summary>
        /// <param name="value">String value</param>
        /// <param name="stringValues">Array of string values to compare</param>
        /// <returns>Return true if any string value matches</returns>
        public static bool In(this string value, params string[] stringValues)
        {
            return stringValues.Any(otherValue => string.CompareOrdinal(value, otherValue) == 0);
        }

        /// <summary>
        /// Converts string to enum object
        /// </summary>
        /// <typeparam name="T">Type of enum</typeparam>
        /// <param name="value">String value to convert</param>
        /// <returns>Returns enum object</returns>
        public static T ToEnum<T>(this string value)
            where T : struct
        {
            return (T)Enum.Parse(typeof(T), value, true);
        }

        /// <summary>
        /// Returns characters from right of specified length
        /// </summary>
        /// <param name="value">String value</param>
        /// <param name="length">Max number of charaters to return</param>
        /// <returns>Returns string from right</returns>
        public static string Right(this string value, int length)
        {
            return value != null && value.Length > length ? value.Substring(value.Length - length) : value;
        }

        /// <summary>
        /// Returns characters from left of specified length
        /// </summary>
        /// <param name="value">String value</param>
        /// <param name="length">Max number of charaters to return</param>
        /// <returns>Returns string from left</returns>
        public static string Left(this string value, int length)
        {
            return value != null && value.Length > length ? value.Substring(0, length) : value;
        }

        /// <summary>
        /// Convert hex String to bytes representation
        /// </summary>
        /// <param name="hexValue">Hex string to convert into bytes</param>
        /// <returns>Bytes of hex string</returns>
        public static byte[] HexToBytes(this string hexValue)
        {
            if (hexValue.Length % 2 != 0)
            {
                throw new ArgumentException(string.Format("HexString cannot be in odd number: {0}", hexValue));
            }

            var retVal = new byte[hexValue.Length / 2];
            for (int i = 0; i < hexValue.Length; i += 2)
            {
                retVal[i / 2] = byte.Parse(hexValue.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
            }

            return retVal;
        }

        public static bool IsMatch(this string value, Regex regex)
        {
            bool result = false;

            try
            {
                Match match = regex.Match(value);
                while (match.Success)
                {
                    if (value == match.Value)
                    {
                        result = true;
                        break;
                    }
                    match = match.NextMatch();
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }

        public static bool IsMatch(this string value, Regex regex, int beginning, int lenght, bool reverse = false)
        {
            bool result = false;

            try
            {
                Match match = regex.Match(value);
                while (match.Success)
                {
                    value = reverse ? value.Substring(value.Length - lenght) : value.Substring(beginning, lenght);
                    if (value == match.Value)
                    {
                        result = true;
                        break;
                    }
                    match = match.NextMatch();
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }

        public static bool IsMatch(this string value, string pattern)
        {
            return value.IsMatch(new Regex(pattern));
        }

        public static bool IsMatch(this string value, string pattern, int beginning, int lenght, bool reverse = false)
        {
            return value.IsMatch(new Regex(pattern), beginning, lenght, reverse);
        }

        public static bool IsValidEmail(this string value)
        {
            return value.IsMatch(new Regex(EMAIL_PATTERN));
        }

        public static bool IsValidURI(this string value)
        {
            return value.IsMatch(new Regex(URI_PATTERN));
        }

        public static bool IsValidIP(this string value)
        {
            return value.IsMatch(new Regex(IP_PATTERN));
        }

        public static bool IsItalyPhoneNumber(this string value)
        {
            return value.IsMatch(new Regex(ITALY_PHONE_NUMBER_PATTERN));
        }

        public static bool IsAllUppercase(this string value)
        {
            return value.IsMatch(new Regex(UPPERCASE_PATTERN));
        }

        public static bool IsAllLowercase(this string value)
        {
            return value.IsMatch(new Regex(LOWERCASE_PATTERN));
        }

        public static bool IsCamelcase(this string value)
        {
            return value.IsMatch(new Regex(CAMELCASE_PATTERN));
        }

        public static bool IsPascalcase(this string value)
        {
            return value.IsMatch(new Regex(PASCALCASE_PATTERN));
        }

        public static string StripEscape(this string value)
        {
            return value.StripEscape(string.Empty);
        }

        public static string StripEscape(this string value, string replacement)
        {
            return Regex.Replace(value, "(\n|\r)+", replacement);
        }

        public static string TrimPunctuation(this string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            value = value.Trim();

            char lastChar = Convert.ToChar(value.Substring(value.Length - 1, 1));
            return char.IsPunctuation(lastChar) ? TrimPunctuation(value.Substring(0, value.Length - 1)) : value;
        }

        [Obsolete("Use StripEscape method instead.")]
        public static string Normalize(this string value, char normalizerChar = ' ')
        {
            char[] chars = value.ToCharArray();

            var escapeCharacters = new char[9];
            escapeCharacters[0] = '\n'; // avanzamento di riga
            escapeCharacters[1] = '\r'; // ritorno ad inizio riga
            escapeCharacters[3] = '\t'; // tabulazione orizzontale
            escapeCharacters[4] = '\v'; // tabulazione verticale
            escapeCharacters[5] = '\b'; // backspace
            escapeCharacters[6] = '\f'; // salto di pagina
            escapeCharacters[7] = '\a'; // allarme sonoro
            escapeCharacters[8] = '\\'; // doppio backslash

            for (int i = 0; i < chars.Length; i++)
            {
                foreach (char c in escapeCharacters)
                {
                    if (chars[i] == c)
                    {
                        chars[i] = normalizerChar;
                    }
                }
            }

            return new string(chars);
        }

        /// <summary>
        /// Ripulisce i caratteri latini dagli accenti
        /// </summary>
        /// <param name="value">stringa da normalizzare</param>
        /// <returns></returns>
        public static string LatinToAscii(this string value)
        {
            string normalize = string.Empty;

            for (int i = 0; i < value.Length; i++)
            {
                string charString = value.Substring(i, 1);
                charString = charString.Normalize(NormalizationForm.FormKD);

                if (charString.Length == 1)
                {
                    normalize += charString;
                }
                else
                {
                    int charsCopied = 0;
                    foreach (char c in charString)
                    {
                        if (c < 128)
                        {
                            normalize += c;
                            charsCopied++;
                        }
                    }

                    if (charsCopied == 0)
                        normalize += value.Substring(i, 1);
                }
            }

            return normalize;
        }

        public static string GetEmptySpaces(int spaces)
        {
            var result = new StringBuilder();

            int i = 1;
            do
            {
                i++;
                result.Append(" ");
            } while (i <= spaces);

            return result.ToString();
        }

        /// <summary>
        /// Appends a formatted string and the default line terminator to this StringBuilder instance.
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        /// <param name="format">A composite format string.</param>
        /// <param name="arguments">Arguments to format.</param>
        /// <returns>The cref="System.Text.StringBuilder".</returns>
        public static StringBuilder AppendLineFormat(this StringBuilder builder, string format, params object[] arguments)
        {
            string value = string.Format(format, arguments);
            builder.AppendLine(value);
            return builder;
        }

        /// <summary>
        /// Appends a formatted string and the default line terminator to this StringBuilder instance.
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        /// <param name="tabs">Number of tabs with which to indent the string.</param>
        /// <param name="format">A composite format string.</param>
        /// <param name="arguments">Arguments to format.</param>
        /// <returns>The cref="System.Text.StringBuilder".</returns>
        public static StringBuilder AppendLineFormat(this StringBuilder builder, int tabs, string format, params object[] arguments)
        {
            string tabsFormat = "";
            int i = 1;
            do
            {
                i++;
                tabsFormat += "    ";
            } while (i <= tabs);

            string value = string.Format(format, arguments);
            return builder.AppendLineFormat("{0}{1}", tabsFormat, value);
        }

        /// <summary>
        /// Appends a string and the default line terminator to this StringBuilder instance.
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        /// <param name="tabs">Number of tabs with which to indent the string.</param>
        /// <param name="value">Value to append.</param>
        /// <returns>The cref="System.Text.StringBuilder".</returns>
        public static StringBuilder AppendLine(this StringBuilder builder, int tabs, string value)
        {
            string tabsFormat = "";
            int i = 1;
            do
            {
                i++;
                tabsFormat += "    ";
            } while (i <= tabs);

            return builder.AppendLineFormat("{0}{1}", tabsFormat, value);
        }

        /// <summary>
        /// Appends a formatted string to this StringBuilder instance.
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        /// <param name="tabs">Number of tabs with which to indent the string.</param>
        /// <param name="format">A composite format string.</param>
        /// <param name="arguments">Arguments to format.</param>
        /// <returns>The cref="System.Text.StringBuilder".</returns>
        public static StringBuilder AppendFormat(this StringBuilder builder, int tabs, string format, params object[] arguments)
        {
            string tabsFormat = "";
            int i = 1;
            do
            {
                i++;
                tabsFormat += "    ";
            } while (i <= tabs);

            string value = string.Format(format, arguments);
            return builder.AppendFormat("{0}{1}", tabsFormat, value);
        }

        /// <summary>
        /// Makes it possible to conditionally append to a StringBuilder while keeping it fluent.
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        /// <param name="condition">Condition to be evaluated.</param>
        /// <param name="value">Value to append.</param>
        /// <returns>The cref="System.Text.StringBuilder".</returns>
        public static StringBuilder AppendIf(this StringBuilder builder, bool condition, string value)
        {
            if (condition) builder.Append(value);
            return builder;
        }

        /// <summary>
        /// Clear the contents of a StringBuilder object
        /// </summary>
        /// <param name="builder">The cref="System.Text.StringBuilder".</param>
        public static void Clear(this StringBuilder builder)
        {
            builder.Length = 0;
        }
    }
}
